{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建卷积神经网络\n",
    "- 卷积网络中的输入和层与传统神经网络有些区别，需重新设计，训练模块基本一致"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\ProgramData\\Anaconda3\\lib\\site-packages\\numpy\\_distributor_init.py:32: UserWarning: loaded more than 1 DLL from .libs:\n",
      "D:\\ProgramData\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.IPBC74C7KURV7CB2PKT5Z5FNR3SIBV4J.gfortran-win_amd64.dll\n",
      "D:\\ProgramData\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.NOIJJG62EMASZI6NYURL6JBKM4EVBGM7.gfortran-win_amd64.dll\n",
      "  stacklevel=1)\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "import torch.nn.functional as F\n",
    "from torchvision import datasets,transforms \n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 首先读取数据\n",
    "- 分别构建训练集和测试集（验证集）\n",
    "- DataLoader来迭代取数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 定义超参数 \n",
    "input_size = 28  #图像的总尺寸28*28\n",
    "num_classes = 10  #标签的种类数\n",
    "num_epochs = 3  #训练的总循环周期\n",
    "batch_size = 64  #一个撮（批次）的大小，64张图片\n",
    "\n",
    "# 训练集\n",
    "train_dataset = datasets.MNIST(root='./data',  \n",
    "                            train=True,   \n",
    "                            transform=transforms.ToTensor(),  \n",
    "                            download=True) \n",
    "\n",
    "# 测试集\n",
    "test_dataset = datasets.MNIST(root='./data', \n",
    "                           train=False, \n",
    "                           transform=transforms.ToTensor())\n",
    "\n",
    "# 构建batch数据\n",
    "train_loader = torch.utils.data.DataLoader(dataset=train_dataset, \n",
    "                                           batch_size=batch_size, \n",
    "                                           shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(dataset=test_dataset, \n",
    "                                           batch_size=batch_size, \n",
    "                                           shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 卷积网络模块构建\n",
    "- 一般卷积层，relu层，池化层可以写成一个套餐\n",
    "- 注意卷积最后结果还是一个特征图，需要把图转换成向量才能做分类或者回归任务"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(CNN, self).__init__()\n",
    "        self.conv1 = nn.Sequential(         # 输入大小 (1, 28, 28)\n",
    "            nn.Conv2d(\n",
    "                in_channels=1,              # 灰度图\n",
    "                out_channels=16,            # 要得到几多少个特征图\n",
    "                kernel_size=5,              # 卷积核大小\n",
    "                stride=1,                   # 步长\n",
    "                padding=2,                  # 如果希望卷积后大小跟原来一样，需要设置padding=(kernel_size-1)/2 if stride=1\n",
    "            ),                              # 输出的特征图为 (16, 28, 28)\n",
    "            nn.ReLU(),                      # relu层\n",
    "            nn.MaxPool2d(kernel_size=2),    # 进行池化操作（2x2 区域）, 输出结果为： (16, 14, 14)\n",
    "        )\n",
    "        self.conv2 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)\n",
    "            nn.Conv2d(16, 32, 5, 1, 2),     # 输出 (32, 14, 14)\n",
    "            nn.ReLU(),                      # relu层\n",
    "            nn.Conv2d(32, 32, 5, 1, 2),\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(2),                # 输出 (32, 7, 7)\n",
    "        )\n",
    "        \n",
    "        self.conv3 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)\n",
    "            nn.Conv2d(32, 64, 5, 1, 2),     # 输出 (32, 14, 14)\n",
    "            nn.ReLU(),             # 输出 (32, 7, 7)\n",
    "        )\n",
    "        \n",
    "        self.out = nn.Linear(64 * 7 * 7, 10)   # 全连接层得到的结果\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = self.conv2(x)\n",
    "        x = self.conv3(x)\n",
    "        x = x.view(x.size(0), -1)           # flatten操作，结果为：(batch_size, 32 * 7 * 7)\n",
    "        output = self.out(x)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准确率作为评估标准"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def accuracy(predictions, labels):\n",
    "    pred = torch.max(predictions.data, 1)[1] \n",
    "    rights = pred.eq(labels.data.view_as(pred)).sum() \n",
    "    return rights, len(labels) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 训练网络模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当前epoch: 0 [0/60000 (0%)]\t损失: 2.300918\t训练集准确率: 10.94%\t测试集正确率: 10.10%\n",
      "当前epoch: 0 [6400/60000 (11%)]\t损失: 0.204191\t训练集准确率: 78.06%\t测试集正确率: 93.31%\n",
      "当前epoch: 0 [12800/60000 (21%)]\t损失: 0.039503\t训练集准确率: 86.51%\t测试集正确率: 96.69%\n",
      "当前epoch: 0 [19200/60000 (32%)]\t损失: 0.057866\t训练集准确率: 89.93%\t测试集正确率: 97.54%\n",
      "当前epoch: 0 [25600/60000 (43%)]\t损失: 0.069566\t训练集准确率: 91.68%\t测试集正确率: 97.68%\n",
      "当前epoch: 0 [32000/60000 (53%)]\t损失: 0.228793\t训练集准确率: 92.85%\t测试集正确率: 98.18%\n",
      "当前epoch: 0 [38400/60000 (64%)]\t损失: 0.111003\t训练集准确率: 93.72%\t测试集正确率: 98.16%\n",
      "当前epoch: 0 [44800/60000 (75%)]\t损失: 0.110226\t训练集准确率: 94.28%\t测试集正确率: 98.44%\n",
      "当前epoch: 0 [51200/60000 (85%)]\t损失: 0.014538\t训练集准确率: 94.78%\t测试集正确率: 98.60%\n",
      "当前epoch: 0 [57600/60000 (96%)]\t损失: 0.051019\t训练集准确率: 95.14%\t测试集正确率: 98.45%\n",
      "当前epoch: 1 [0/60000 (0%)]\t损失: 0.036383\t训练集准确率: 98.44%\t测试集正确率: 98.68%\n",
      "当前epoch: 1 [6400/60000 (11%)]\t损失: 0.088116\t训练集准确率: 98.50%\t测试集正确率: 98.37%\n",
      "当前epoch: 1 [12800/60000 (21%)]\t损失: 0.120306\t训练集准确率: 98.59%\t测试集正确率: 98.97%\n",
      "当前epoch: 1 [19200/60000 (32%)]\t损失: 0.030676\t训练集准确率: 98.63%\t测试集正确率: 98.83%\n",
      "当前epoch: 1 [25600/60000 (43%)]\t损失: 0.068475\t训练集准确率: 98.59%\t测试集正确率: 98.87%\n",
      "当前epoch: 1 [32000/60000 (53%)]\t损失: 0.033244\t训练集准确率: 98.62%\t测试集正确率: 99.03%\n",
      "当前epoch: 1 [38400/60000 (64%)]\t损失: 0.024162\t训练集准确率: 98.67%\t测试集正确率: 98.81%\n",
      "当前epoch: 1 [44800/60000 (75%)]\t损失: 0.006713\t训练集准确率: 98.69%\t测试集正确率: 98.17%\n",
      "当前epoch: 1 [51200/60000 (85%)]\t损失: 0.009284\t训练集准确率: 98.69%\t测试集正确率: 98.97%\n",
      "当前epoch: 1 [57600/60000 (96%)]\t损失: 0.036536\t训练集准确率: 98.68%\t测试集正确率: 98.97%\n",
      "当前epoch: 2 [0/60000 (0%)]\t损失: 0.125235\t训练集准确率: 98.44%\t测试集正确率: 98.73%\n",
      "当前epoch: 2 [6400/60000 (11%)]\t损失: 0.028075\t训练集准确率: 99.13%\t测试集正确率: 99.17%\n",
      "当前epoch: 2 [12800/60000 (21%)]\t损失: 0.029663\t训练集准确率: 99.26%\t测试集正确率: 98.39%\n",
      "当前epoch: 2 [19200/60000 (32%)]\t损失: 0.073855\t训练集准确率: 99.20%\t测试集正确率: 98.81%\n",
      "当前epoch: 2 [25600/60000 (43%)]\t损失: 0.018130\t训练集准确率: 99.16%\t测试集正确率: 99.09%\n",
      "当前epoch: 2 [32000/60000 (53%)]\t损失: 0.006968\t训练集准确率: 99.15%\t测试集正确率: 99.11%\n"
     ]
    }
   ],
   "source": [
    "# 实例化\n",
    "net = CNN() \n",
    "#损失函数\n",
    "criterion = nn.CrossEntropyLoss() \n",
    "#优化器\n",
    "optimizer = optim.Adam(net.parameters(), lr=0.001) #定义优化器，普通的随机梯度下降算法\n",
    "\n",
    "#开始训练循环\n",
    "for epoch in range(num_epochs):\n",
    "    #当前epoch的结果保存下来\n",
    "    train_rights = [] \n",
    "    \n",
    "    for batch_idx, (data, target) in enumerate(train_loader):  #针对容器中的每一个批进行循环\n",
    "        net.train()                             \n",
    "        output = net(data) \n",
    "        loss = criterion(output, target) \n",
    "        optimizer.zero_grad() \n",
    "        loss.backward() \n",
    "        optimizer.step() \n",
    "        right = accuracy(output, target) \n",
    "        train_rights.append(right) \n",
    "\n",
    "    \n",
    "        if batch_idx % 100 == 0: \n",
    "            \n",
    "            net.eval() \n",
    "            val_rights = [] \n",
    "            \n",
    "            for (data, target) in test_loader:\n",
    "                output = net(data) \n",
    "                right = accuracy(output, target) \n",
    "                val_rights.append(right)\n",
    "                \n",
    "            #准确率计算\n",
    "            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))\n",
    "            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))\n",
    "\n",
    "            print('当前epoch: {} [{}/{} ({:.0f}%)]\\t损失: {:.6f}\\t训练集准确率: {:.2f}%\\t测试集正确率: {:.2f}%'.format(\n",
    "                epoch, batch_idx * batch_size, len(train_loader.dataset),\n",
    "                100. * batch_idx / len(train_loader), \n",
    "                loss.data, \n",
    "                100. * train_r[0].numpy() / train_r[1], \n",
    "                100. * val_r[0].numpy() / val_r[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### 练习\n",
    "- 再加入一层卷积，效果怎么样？\n",
    "- 当前任务中为什么全连接层是32*7*7 其中每一个数字代表什么含义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
